using System;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Collections.Generic;

namespace ReactiveCode.Collections
{
    using Observables;
    using Linq;
    
    public class ObservableLinkedList<T>: IEnumerable<T>, IObservableEnumerable<T>
    {
        internal class List: LinkedList<ObservableLinkedListNode<T>>
        {
            internal ObservableLinkedList<T> _owner;

            public List(ObservableLinkedList<T> owner)
            {
                Contract.Requires(owner != null);
                _owner = owner;
            }
        }

        [ContractInvariantMethod]
        void ObjectInvariant()
        {
            Contract.Invariant(_list != null && _listeners != null);
            Contract.Invariant(Contract.ForAll(_list, n => n.Value != null));
            Contract.Invariant((Count == 0) == (First == null));
            Contract.Invariant((Count == 0) == (Last == null));
            Contract.Invariant(Count > 1 || Last == First);
        }

        List _list;

        public ObservableLinkedListNode<T> First
        {
            get { return _list.First != null ? _list.First.Value : null; }
        }

        public ObservableLinkedListNode<T> Last
        {
            get { return _list.Last != null ? _list.Last.Value : null; }
        }

        public ObservableLinkedList()
        {
            _list = new List(this);
        }
        public ObservableLinkedList(IEnumerable<T> collection)
            : this()
        {
            Contract.Requires<ArgumentNullException>(collection != null, "collection");
            foreach(var value in collection)
                AddLast(value);
        }

        #region Add

        public void AddAfter(ObservableLinkedListNode<T> node, ObservableLinkedListNode<T> newNode)
        {
            Contract.Requires<ArgumentNullException>(node != null, "node");
            Contract.Requires<ArgumentNullException>(newNode != null, "newNode");
            Contract.Requires<ArgumentException>(node.List == this, "node");
            Contract.Requires<ArgumentException>(newNode.List == null, "newNode");
            Contract.Ensures(Count == Contract.OldValue<int>(Count) + 1);
            Contract.Ensures(node.Next == newNode && newNode.Previous == node);
            _list.AddAfter(node.Node, newNode.Node);
            _listeners.OnAdded(newNode, -1);
        }
        public ObservableLinkedListNode<T> AddAfter(ObservableLinkedListNode<T> node, T value)
        {
            Contract.Requires<ArgumentNullException>(node != null, "node");
            Contract.Requires<ArgumentException>(node.List == this, "node");
            Contract.Ensures(Count == Contract.OldValue<int>(Count) + 1);
            Contract.Ensures(node.Next != null && Equals(node.Next.Value, value));
            var newNode = new ObservableLinkedListNode<T>(value);
            AddAfter(node, newNode);
            return newNode;
        }
        public void AddBefore(ObservableLinkedListNode<T> node, ObservableLinkedListNode<T> newNode)
        {
            Contract.Requires<ArgumentNullException>(node != null, "node");
            Contract.Requires<ArgumentNullException>(newNode != null, "newNode");
            Contract.Requires<ArgumentException>(node.List == this, "node");
            Contract.Requires<ArgumentException>(newNode.List == null, "newNode");
            Contract.Ensures(Count == Contract.OldValue<int>(Count) + 1);
            Contract.Ensures(node.Previous == newNode && newNode.Next == node);
            _list.AddBefore(node.Node, newNode.Node);
            _listeners.OnAdded(newNode, -1);
        }
        public ObservableLinkedListNode<T> AddBefore(ObservableLinkedListNode<T> node, T value)
        {
            Contract.Requires<ArgumentNullException>(node != null, "node");
            Contract.Requires<ArgumentException>(node.List == this, "node");
            Contract.Ensures(Count == Contract.OldValue<int>(Count) + 1);
            Contract.Ensures(node.Previous != null && Equals(node.Previous.Value, value));
            var newNode = new ObservableLinkedListNode<T>(value);
            AddBefore(node, newNode);
            return newNode;
        }
        public void AddFirst(ObservableLinkedListNode<T> node)
        {
            Contract.Requires<ArgumentNullException>(node != null, "node");
            Contract.Requires<ArgumentException>(node.List == null, "node");
            Contract.Ensures(Count == Contract.OldValue<int>(Count) + 1);
            Contract.Ensures(First == node);
            _list.AddFirst(node.Node);
            _listeners.OnAdded(node, -1);
        }
        public ObservableLinkedListNode<T> AddFirst(T value)
        {
            Contract.Ensures(Count == Contract.OldValue<int>(Count) + 1);
            Contract.Ensures(First != null && Equals(First.Value, value));
            var node = new ObservableLinkedListNode<T>(value);
            AddFirst(node);
            return node;
        }
        public void AddLast(ObservableLinkedListNode<T> node)
        {
            Contract.Requires<ArgumentNullException>(node != null, "node");
            Contract.Requires<ArgumentException>(node.List == null, "node");
            Contract.Ensures(Count == Contract.OldValue<int>(Count) + 1);
            Contract.Ensures(Last == node);
            _list.AddLast(node.Node);
            _listeners.OnAdded(node, -1);
        }
        public ObservableLinkedListNode<T> AddLast(T value)
        {
            Contract.Ensures(Count == Contract.OldValue<int>(Count) + 1);
            Contract.Ensures(Last != null && Equals(Last.Value, value));
            var node = new ObservableLinkedListNode<T>(value);
            AddLast(node);
            return node;
        }

        #endregion

        public int Count
        {
            [Pure]
            get { return _list.Count; }
        }

        #region Remove

        public void Remove(ObservableLinkedListNode<T> node)
        {
            Contract.Requires<ArgumentNullException>(node != null, "node");
            Contract.Ensures(Count == Contract.OldValue<int>(Count) - 1);
            Contract.Ensures(node.List == null && node.Previous == null && node.Next == null);
            _list.Remove(node.Node);
            _listeners.OnRemoved(node, -1);
        }
        public bool Remove(T value)
        {
            var node = Find(value);
            if (node == null)
                return false;
            Contract.Assume(Count > 0);
            Remove(node);
            return true;
        }
        public void RemoveFirst()
        {
            Contract.Requires<InvalidOperationException>(Count > 0);
            Contract.Ensures(Count == Contract.OldValue<int>(Count) - 1);
            Contract.Ensures(Contract.OldValue<ObservableLinkedListNode<T>>(First) != First);
            var first = this.First;
            _list.RemoveFirst();
            Contract.Assume(first != null);
            _listeners.OnRemoved(first, -1);
        }
        public void RemoveLast()
        {
            Contract.Requires<InvalidOperationException>(Count > 0);
            Contract.Ensures(Count == Contract.OldValue<int>(Count) - 1);
            Contract.Ensures(Contract.OldValue<ObservableLinkedListNode<T>>(Last) != Last);
            var last = Last;
            _list.RemoveLast();
            Contract.Assume(last != null);
            _listeners.OnRemoved(last, -1);
        }

        #endregion

        public void Clear()
        {
            Contract.Ensures(Count == 0);
            _list.Clear();
            _listeners.OnCleared();
        }

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            foreach (var node in _list)
                yield return node.Value;
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region IObservableEnumerable<T> Members

        IEnumerableObserver<T> _listeners = EnumerableObserver.Empty<T>();

        public IDisposable Subscribe(IEnumerableObserver<T> observer)
        {
            var observable = _list as IObservableEnumerable<T>;
            if (observable == null)
            {
                var subject = new EnumerableSubject<T>();
                _listeners = subject;
                observable = subject;
            }
            observer.OnReset(_list.Cast<IObservable<T>>());
            return observable.Subscribe(observer);
        }

        #endregion

        public void CopyTo(T[] array, int index)
        {
            Contract.Requires<ArgumentNullException>(array != null, "array");
            Contract.Requires<ArgumentOutOfRangeException>(index >= 0 && index < array.Length, "index");
            Contract.Requires<ArgumentException>(Count == 0 || index + Count <= array.Length);

            foreach (T value in this)
            {
                Contract.Assume(index < array.Length);
                array[index++] = value;
            }
        }
        [Pure]
        public ObservableLinkedListNode<T> Find(T value)
        {
            return _list.FirstOrDefault(n => EqualityComparer<T>.Default.Equals(n.Value, value));
        }
        [Pure]
        public ObservableLinkedListNode<T> FindLast(T value)
        {
            for (var n = _list.Last; n != null; n = n.Previous)
            {
                Contract.Assume(n.Value != null);
                if (EqualityComparer<T>.Default.Equals(n.Value.Value, value))
                    return n.Value;
            }
            return null;
        }

        [Pure]
        public bool Contains(T value)
        {
            return this.Find(value) != null;
        }
    }

    public class ObservableLinkedListNode<T>: IObservable<T>
    {
        internal LinkedListNode<ObservableLinkedListNode<T>> Node { get; private set; }

        [ContractInvariantMethod]
        void ObjectInvariant()
        {
            Contract.Invariant(_var != null);
            Contract.Invariant(Node != null);
            Contract.Invariant(Node.Value == this);
        }

        Variable<T> _var;
        public T Value
        {
            [Pure]
            get { return _var.Value; }
            set { _var.Value = value; }
        }

        public ObservableLinkedList<T> List
        {
            [Pure]
            get
            {
                var list = Node.List as ObservableLinkedList<T>.List;
                return list != null ? list._owner : null;
            }
        }

        public ObservableLinkedListNode<T> Previous
        {
            [Pure]
            get
            { 
                var prev = Node.Previous;
                if (prev == null)
                    return null;
                return prev.Value;
            }
        }

        public ObservableLinkedListNode<T> Next
        {
            [Pure]
            get
            {
                var next = Node.Next;
                if (next == null)
                    return null;
                return next.Value;
            }
        }

        public ObservableLinkedListNode(T value)
        {
            _var = new Variable<T>(value);
            Node = new LinkedListNode<ObservableLinkedListNode<T>>(this);
            Contract.Assert(this == Node.Value);
        }

        public IDisposable Subscribe(IObserver<T> observer)
        {
            return _var.Subscribe(observer);
        }
    }
}